/*
* Licensed under the MIT License
* 
* Copyright (c) 2010 Kay Kreuning
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package nl.hyves.api.rpc
{
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import nl.hyves.api.rpc.IAsyncToken;
	
	[Event(name="complete", type="flash.events.Event")]
	[Event(name="error", type="flash.events.ErrorEvent")]
	[Event(name="ioError", type="flash.events.IOErrorEvent")]
	
	public class AsyncToken extends EventDispatcher implements IAsyncToken
	{
		private var responders:Array;
		private var responseIsOK:Boolean;
		private var response:Object;
		private var _data:Object;
		
		private var urlRequest:URLRequest;
		private var validator:Function;
		private var resultParser:Function;
		private var faultParser:Function;
		
		public function get data():Object
		{
			return _data;
		}
		
		public function get rawResponse():Object
		{
			return response;
		}
		
		public function AsyncToken(urlRequest:URLRequest, validator:Function = null, resultParser:Function = null, faultParser:Function = null)
		{
			this.urlRequest = urlRequest;
			
			var urlLoader:URLLoader = new URLLoader(urlRequest);
			urlLoader.addEventListener(Event.COMPLETE, onComplete);
			urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			
			this.validator = validator;
			this.resultParser = resultParser;
			this.faultParser = faultParser;
		}
		
		public function hasResponder():Boolean
		{
			return responders ? responders.length > 0 : false;
		}
		
		public function addResponder(result:Function, fault:Function = null):void
		{
			// If there is already data available we can call the response immediately:
			if (data)
			{
				responseIsOK ? result(data) : fault(data);
				
				// Exit the method:
				return;
			}
			
			if (!responders)
				responders = new Array();
			
			var responder:Responder = new Responder(result, fault);
			
			responders.push(responder);
		}
		
		private function onComplete(event:Event):void
		{
			// Assign the raw response:
			response = event.target.data;
			
			// If we have a validator validate the response:
			if (validator != null)
			{
				// Validate:
				responseIsOK = validator(response);
				
				if (!responseIsOK)
				{
					// Set the data, if we have a parser, parse the response first, otherwist just assign the response:
					_data = faultParser != null ? faultParser(response) : response;
					
					// Invoke the responders and dispatch the error event:
					while (hasResponder())
					{
						var responder:Responder = responders.pop();
						
						// A responder's fault method can be null, check first:
						if (responder.fault != null)
							responder.fault.apply(null, [data]);
					}
					
					dispatchEvent(new ErrorEvent(ErrorEvent.ERROR));
					
					// Exit the method:
					return;
				}
			}
			
			// If there is no validator or there is a validator and the response is OK we get at this point.
			responseIsOK = true;
			
			// Set the data, if we have a parser, parse the response first, othwerwise just assign the response:
			_data = resultParser != null ? resultParser(response) : response;
				
			
			// Now invoke the responders and dispatch the complete event:
			while (hasResponder())
				responders.pop().result.apply(null, [data]);
				
			dispatchEvent(new Event(Event.COMPLETE));
		}
		
		private function onIOError(event:IOErrorEvent):void
		{
			if (hasEventListener(IOErrorEvent.IO_ERROR))
				dispatchEvent(event.clone());
		}
	}
}

internal class Responder
{
	public var result:Function;
	public var fault:Function;
	
	public function Responder(result:Function, fault:Function)
	{
		this.result = result;
		this.fault = fault;
	}
}